package main

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"encoding/gob"
	"fmt"
	"log"
	"math/big"
)

type Transaction struct {
	TXID      []byte
	TXInputs  []TXInput
	TXOutputs []TXOutput
}

type TXInput struct {
	TXid  []byte
	Index int64

	//数字签名
	Signature []byte
	//出款方公钥哈希
	Pubkey []byte
}

type TXOutput struct {
	Value float64
	//收款方公钥哈希
	PubKeyHash []byte
}

func (output TXOutput) lock(address string) {
	addressByte := Base58Decode([]byte(address))
	len := len(addressByte)
	pubKeyHash := addressByte[1 : len-4]
	output.PubKeyHash = pubKeyHash
}

func NewTXOutput(value float64, address string) *TXOutput {
	output := TXOutput{
		Value: value,
	}
	output.lock(address)
	return &output
}

func (tx *Transaction) SetHash() {
	var buffer bytes.Buffer
	encoder := gob.NewEncoder(&buffer)
	err := encoder.Encode(tx)
	if err != nil {
		log.Panic(err, "编码出错!")
	}
	data := buffer.Bytes()
	hash := sha256.Sum256(data)
	tx.TXID = hash[:]
}

const reward = 12.5

func NewCoinbaseTX(address string, data string) *Transaction {
	input := TXInput{[]byte{}, -1, nil, []byte(data)}
	//output := TXOutput{reward, address}
	output := NewTXOutput(reward, address)
	tx := Transaction{
		[]byte{},
		[]TXInput{input},
		[]TXOutput{*output},
	}
	tx.SetHash()
	return &tx
}

func (tx *Transaction) IScoinbase() bool {
	/*if len(tx.TXInputs) == 1 {
		input := tx.TXInputs[0]
		if bytes.Equal(input.TXid, []byte{}) && input.Index == -1 {
			return true
		}
	}
	return false*/
	if len(tx.TXInputs) == 1 && len(tx.TXInputs[0].TXid) == 0 && tx.TXInputs[0].Index == -1 {
		return true
	}
	return false
}

func NewTransaction(from, to string, amount float64, bc *BlockChain) *Transaction {
	// 找到自己的钱包，找到公钥和私钥
	ws := NewWallets()
	wallet := ws.WalletsMap[from]
	if wallet == nil {
		fmt.Println("没有找到该地址的钱包,交易创建失败")
		return nil
	}
	pubKey := wallet.PubKey
	privateKey := wallet.Private
	pubKeyHash := HashPubKey(pubKey)
	utxos, resValue := bc.FindNeedUTXOs(pubKeyHash, amount)
	var inputs []TXInput
	var outputs []TXOutput

	if resValue < amount {
		fmt.Println("余额不足，交易失败")
		return nil
	}

	for id, indexArray := range utxos {
		for _, i := range indexArray {
			input := TXInput{[]byte(id), int64(i), nil, pubKey}
			inputs = append(inputs, input)
		}
	}
	//output := TXOutput{amount, to}
	output := NewTXOutput(amount, to)
	outputs = append(outputs, *output)
	if resValue > amount {
		output = NewTXOutput(resValue-amount, from)
		outputs = append(outputs, *output)
	}
	tx := Transaction{[]byte{}, inputs, outputs}
	tx.SetHash()
	//签名,交易创建的最后进行签名
	bc.SignTransaction(&tx, privateKey)
	return &tx
}

func (tx *Transaction) Sign(prviateKey *ecdsa.PrivateKey, prevTXs map[string]Transaction) {
	txCopy := tx.TrimmedCopy()
	for i, input := range txCopy.TXInputs {
		prevTx := prevTXs[string(input.TXid)]
		if len(prevTx.TXID) == 0 {
			log.Panic("引用的交易无效")
		}
		txCopy.TXInputs[i].Pubkey = prevTx.TXOutputs[input.Index].PubKeyHash
		txCopy.SetHash()
		txCopy.TXInputs[i].Pubkey = nil
		signDataHash := txCopy.TXID
		r, s, err := ecdsa.Sign(rand.Reader, prviateKey, signDataHash)
		if err != nil {
			log.Panic(err)
		}
		singnature := append(r.Bytes(), s.Bytes()...)
		tx.TXInputs[i].Signature = singnature
	}
}

func (tx *Transaction) TrimmedCopy() Transaction {
	var inputs []TXInput
	var outputs []TXOutput
	for _, input := range tx.TXInputs {
		inputs = append(inputs, TXInput{input.TXid, input.Index, nil, nil})
	}
	for _, output := range tx.TXOutputs {
		outputs = append(outputs, output)
	}
	return Transaction{tx.TXID, inputs, outputs}
}

func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
	if tx.IScoinbase() {
		return true
	}
	txCopy := tx.TrimmedCopy()
	for i, input := range tx.TXInputs {
		prevTX := prevTXs[string(input.TXid)]
		if len(prevTX.TXID) == 0 {
			log.Panic("引用的交易无效")
		}
		txCopy.TXInputs[i].Pubkey = prevTX.TXOutputs[input.Index].PubKeyHash
		txCopy.SetHash()
		dataHash := txCopy.TXID
		signature := input.Signature
		pubKey := input.Pubkey

		r := big.Int{}
		s := big.Int{}
		r.SetBytes(signature[:len(signature)/2])
		s.SetBytes(signature[len(signature)/2:])

		X := big.Int{}
		Y := big.Int{}
		X.SetBytes(pubKey[:len(pubKey)/2])
		Y.SetBytes(pubKey[len(pubKey)/2:])

		pubKeyOrgin := ecdsa.PublicKey{elliptic.P256(), &X, &Y}
		if !ecdsa.Verify(&pubKeyOrgin, dataHash, &r, &s) {
			return false
		}
	}
	return true
}

func (bc *BlockChain) VerifyTransaction(tx *Transaction) bool {
	prevTXs := make(map[string]Transaction)
	for _, input := range tx.TXInputs {
		tx, err := bc.FindTransactionByTXid(input.TXid)
		if err != nil {
			log.Panic(err)
		}
		prevTXs[string(input.TXid)] = tx
	}
	return tx.Verify(prevTXs)
}
